package redis

import (
	"errors"
	"fmt"
	"github.com/gomodule/redigo/redis"
	"log"
	"strings"
)

//redis-sentinel
type Sentinel struct {
	monitor             string    //监听集群名称
	masterAddressChange chan bool //主服地址
	sentinelNodes       []*Node   //连接sentinel的配置

	masterNode *Node //连接redis主服务配置
}

func NewSentinel(monitor string, address ...string) *Sentinel {
	this := new(Sentinel)

	for _, v := range address {
		this.sentinelNodes = append(this.sentinelNodes, &Node{Address: v})
	}

	this.monitor = monitor
	this.masterAddressChange = make(chan bool)
	if err := this.switchMaster(); err != nil {
		log.Println("初始化客户端失败：", err)
	}

	go this.listen()
	return this
}

//输出连接
func (this *Sentinel) Conn() redis.Conn {
	return this.masterNode.Conn()
}

//配置详细的节点信息
func (this *Sentinel) SetSentinelNodes(nodes ...*Node) *Sentinel {
	readOrCreateNode := func(node *Node) {
		for k := range this.sentinelNodes {
			if this.sentinelNodes[k].Address == node.Address {
				//更新
				this.sentinelNodes[k] = node
				return
			}
		}

		//添加
		this.sentinelNodes = append(this.sentinelNodes, node)
		return
	}

	for k := range nodes {
		readOrCreateNode(nodes[k])
	}

	return this

}

//配置连接redis主服务参数
func (this *Sentinel) SetMasterNode(option *Node) *Sentinel {
	this.masterNode = option
	return this
}

//通过sentinel设置主服务地址
func (this *Sentinel) switchMaster() (err error) {
	var address string
	if this.masterNode == nil {
		this.masterNode = new(Node)
	}

	if address, err = this.masterAddress(); err != nil {
		return
	}

	if this.masterNode.Address != address {
		this.masterNode.Address = address
		this.masterNode.NewRedisPool()
		log.Printf("新生成redis客服端,服务端地址:%s\n", this.masterNode.Address)
	}

	return nil
}

//监听主库切换
func (this *Sentinel) listen() {
	if err := this.createSubscriberForAllClient(); err != nil {
		log.Println("createSubscriberForAllClient：", err)
		return
	}

	for {
		select {
		case <-this.masterAddressChange:
			//主服切换了
			if err := this.switchMaster(); err != nil {
				log.Println("主服切换了,重新生成客户端：", err)
			}
		}
	}
}

//获取redis主服务地址
func (this *Sentinel) masterAddress() (address string, err error) {

	for _, v := range this.sentinelNodes {
		if address, err = this.getMasterAddrByName(v.Address, v.Password, this.monitor); err == nil {
			return
		}

		log.Printf("从%s获取主服地址失败:%s", v.Address, err)
	}

	err = errors.New("无可用的sentinel地址，请检查")

	return
}

//获取主服名称
func (this *Sentinel) getMasterAddrByName(address, password, monitor string) (masterAddress string, err error) {
	var (
		conn  redis.Conn
		reply interface{}
		ss    []interface{}
	)

	if conn, err = redis.Dial("tcp", address); err != nil {
		return
	}

	//验证redis密码
	if password != "" {
		if _, err = conn.Do("AUTH", password); err != nil {
			conn.Close()
			return
		}
	}

	if reply, err = conn.Do("SENTINEL", "get-master-addr-by-name", monitor); err != nil {
		return
	}

	if ss, err = redis.Values(reply, err); err != nil {
		return
	}

	masterAddress = fmt.Sprintf("%s:%s", ss[0], ss[1])

	return

}

//为sentinel客户端对应的redis-sentinel 创建一个监听
func (this *Sentinel) createSubscriberForAllClient() (err error) {
	for _, cli := range this.sentinelNodes {
		subscriber := new(Subscriber)
		if err = subscriber.Connect(cli.Address, cli.Password); err != nil {
			continue
		}

		if err = subscriber.Subscribe("+switch-master", func(channel, massage string) {
			monitor, address := this.parseAddress(massage)
			if monitor == this.monitor && this.masterNode.Address != address {
				//主服发生了切换
				this.masterAddressChange <- true
			}

		}); err != nil {
			continue
		}
	}

	return
}

//解析订阅到的主从切换的消息
func (this *Sentinel) parseAddress(msg string) (monitor string, address string) {
	msgArr := strings.Split(msg, " ")

	if len(msgArr) < 3 {
		return
	}

	return msgArr[0], fmt.Sprintf("%s:%s", msgArr[len(msgArr)-2], msgArr[len(msgArr)-1])

}
